/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.vm;

import axil.api.AxilObject;
import axil.api.Configuration;
import axil.api.Context;
import axil.api.Persona;
import axil.api.extend.Environment;
import axil.api.extend.Parameters;
import axil.engine.Config;
import axil.engine.Repository;
import axil.vm.output.MeteredWriter;
import axil.vm.output.SinkWriter;

import java.io.Writer;

import static axil.framework.Functions.*;


/**
 * The environment under which a script is compiled and executed. The environment
 * is created for eac invocation and then discarded.
 */
public class Env implements Environment {
	private final Config config;
	private final Persona persona;
	private final Context context;
	private final Writer output;


	public Env(Config config, Persona persona, Context context, Writer stream) {
		assert config != null;
		assert persona != null;
		assert context != null;

		this.config = config.seal();
		this.persona = persona;
		this.context = context;

		if (stream == null) {
			stream = new SinkWriter();
		}
		if (config.output() > 0) {
			stream = new MeteredWriter(stream, config.output());
		}
		this.output = stream;
	}


	/**
	 * Compute the size at which an object becomes significant. That is, the
	 * tipping point at which an object of this size will activate the memory
	 * safety mechanisms. This is based upon the maximum number of stack frames
	 * and the size of memory allowed.
	 */
	private int significant() {
		// TODO: Implement as below
        // This script should help for testing
        // "inject(range(1, 5000), 0, func(total, n, (total & 'x' & (((n / 10) ^ 2) / 3))))"
		/*

		return config.memory() / 50;

		 */
		throw unimplemented();
	}


	/**
	 * Check to see if this environment will allow a single object to be of the
	 * given size.
	 */
	public void allow(Parameters params, int size) {
		/**
		 * Since Axil is functional and all objects are immutable, we can keep
		 * track of the overall size of things by simply keeping on eye on the
		 * objects currently on the stack and the size of this new object. Since
		 * the stack itself is self-limiting, we can put a performance
		 * optimization here as well, where we only bother to check the size if
		 * the object is of "significant" size.
		 */
		// TODO: Put this safety mechanism in...
        // This script should help for testing
        // "inject(range(1, 10000), 0, func(bigString, n, bigString & 'x' & new(\"string\", n)))"
        /*
          if (memcheck)
          {
              if (size > significant)
              {
                  if (size > max)
                  {
                      throw error(axil(), "max-memory-exceeded");
                  }
                  StackFrame sf = (StackFrame)params;
                  if ((sf.size() + size) > max)
                  {
                      throw error(axil(), "max-memory-exceeded");
                  }
              }
          }
           */

	}


	/**
	 * Get a constant from the run-time environment by its unique identity. This
	 * is safe to invoke at any time, since the Axil compiler has already
	 * verified that the constant exists.
	 *
	 * @param ident
	 * 	The unique identity of the constant. The string given cannot be null or
	 * 	empty.
	 *
	 * @return
	 * 	Returns a non-null Axil object. If the constant does not exist, then an
	 * 	exception is thrown.
	 */
	public AxilObject constant(String ident) {
		return Repository.instance().constant(ident).value();
	}


	/**
	 * Get an object from the context. The entire context chain is traversed,
	 * if necessary, starting from the top-most context.
	 *
	 * @param ident
	 * 	The identity of the root object. For example, for the host application
	 * 	object reference "Shopping-Cart.Items", the string "Shopping-Cart" would
	 * 	be given, not the full reference.
	 *
	 * @return
	 * 	Returns a non-null Axil object. If the object does not exist, then an
	 * 	exception is thrown.
	 */
	public AxilObject context(String ident) {
		AxilObject a = context().at(ident);
		if (a == null) {
			throw abort("No such object exists in the context.",
			            nv("identity", ident));
		}
		return a;
	}


	/**
	 * Access the context associated with this script execution.
	 *
	 * @return
	 * 	Returns the context, which is never null.
	 */
	public Context context() {
		return context;
	}


	/**
	 * Get the current configuration associated with the execution of the
	 * script. The configuration returned has already been validated for
	 * correctness and sealed from modified.
	 *
	 * @return
	 * 	Returns the current configuration, which is never null.
	 */
	public Configuration configuration() {
		return config;
	}


	/**
	 * Get a handle to the output stream where any print() expressions from the
	 * script will write their output.
	 *
	 * @return
	 * 	Returns a non-null output stream. Even if output is disabled, a non-null
	 * 	(data sink) object is returned.
	 */
	public Writer output() {
		return output;
	}


	/**
	 * Get the persona associated with execution of this script. This persona
	 * logically represents a user or other entity. The persona returned is
	 * never null.
	 *
	 * @return
	 * 	Returns the current persona. The object returned is never null.
	 */
	public Persona persona() {
		return persona;
	}
}
